using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Drawing;

namespace Shrapnel
{
    /// <summary>
    /// Adds functionality to open and save files.
    /// </summary>
	public partial class View
	{
        /// <summary>
		/// List of registered types. These are needed for (de)serialization. Any custom
		/// classes that need to be (de)serialized as part of this view, need to be 
		/// registered here by the user.
		/// </summary>
		private List<Type> registeredTypes = new List<Type>();

		/// <summary>
		/// A list of all types we support internally for serialization, this list only needs
		/// to include any classes that are not directly referenced through our elements. For now
		/// this only includes our AreaPart subclasses.
		/// </summary>
		private Type[] internalRegisteredTypes = new Type[] { 
			typeof(Areas.Box),
			typeof(Areas.Line) 
		};


		/// <summary>
		/// List of registered types. These are needed for (de)serialization. Any custom
		/// classes that need to be (de)serialized as part of this view, need to be 
		/// registered here by the user.
		/// </summary>
		public List<Type> RegisteredTypes
		{
			get { return registeredTypes; }
		}


		/// <summary>
		/// Checks if this workspace view is dirty.
		/// </summary>
		public bool IsDirty
		{
			get
            {
                // TODO: Serialize the view and compare it with the previous serialization.
                return false;
            }
		}

        /// <summary>
        /// Load elements from a file, including positioning, wiring and view
        /// </summary>
        /// <param name="path">The path to the shrapnel file to load from.</param>
        public void LoadFromFile(string path)
        {
            using (FileStream stream = new FileStream(path, FileMode.Open))
			    LoadFromStream(stream);
        }

        /// <summary>
        /// Saves all the elements to a file, including positioning, wiring and view
        /// </summary>
        /// <param name="path">The path of where the shrapnel file will be saved.</param>
        public void SaveToFile(string path)
        {
            using (FileStream stream = new FileStream(path, FileMode.Create))
                SaveToStream(stream);
        }


		/// <summary>
		/// Load elements from a stream, including positioning, wiring and view
		/// </summary>
		/// <param name="inputStream">The stream to load from</param>
		public void LoadFromStream(Stream inputStream)
		{
            layers.Clear();
			selection.Clear();
			viewState.ZoomFactor = 1.0f;
			viewState.Offset = new PointF(0, 0);
			interaction.Reset();

			XmlTextReader reader = new XmlTextReader(inputStream);

			reader.ReadStartElement("View");

			// Our Layer elements
			DataContractSerializer serializerLayers = new DataContractSerializer(
				layers.GetType(), // type
				"Layers", // root name
				"",
				GetKnownSerializationTypes(), // known types
				0x7FFF, // maxItemsInObjectGraph
				false, // ignoreExtensionDataObject
				true, // preserveObjectReferences
				null // dataContractSurrogate
				);

			layers = (List<Layer>)serializerLayers.ReadObject(reader);

			RecalculateElementAreas();

			reader.ReadEndElement();

			ZoomAllInView();

			Invalidate();
		}


		/// <summary>
		/// Saves all the elements to a stream, including positioning, wiring and view
		/// </summary>
		/// <param name="outputStream">The stream to save to</param>
		public void SaveToStream(Stream outputStream)
        {
            XmlTextWriter writer = new XmlTextWriter(outputStream, System.Text.Encoding.ASCII);

			writer.WriteStartDocument();
			writer.WriteStartElement("View");
		
			// Our Layer elements
			DataContractSerializer serializerLayers = new DataContractSerializer(
				layers.GetType(), // type
				"Layers", // root name
				"",
				GetKnownSerializationTypes(), // known types
				0x7FFF, // maxItemsInObjectGraph
				false, // ignoreExtensionDataObject
				true, // preserveObjectReferences
				null // dataContractSurrogate
				);

			serializerLayers.WriteObject(writer, layers);


			// We currently don't store our viewstate, since that probably is not a thing we like? Do we?


			writer.WriteEndElement();
			writer.WriteEndDocument();

			writer.Flush();
		}

		/// <summary>
		/// Force the recalculation of all element areas. This is done after deserializing a graph.
		/// </summary>
		private void RecalculateElementAreas()
		{
			foreach (Layer layer in layers)
			{
				// Force an update of all bounding boxes
				foreach (Element element in layer)
				{
					element.OccupiedArea.Update();

					Node node = element as Node;
					if (node != null)
					{
						foreach (Connector connector in node.Connectors)
						{
							connector.OccupiedArea.Update();
						}
					}
				}

				// Recreate cable areas
				foreach (Element element in layer)
				{
					Cable cable = element as Cable;
					if (cable != null)
					{
						cable.RecalculateOccupiedArea();
					}
				}
			}
		}

		/// <summary>
		/// Get a list of all known types that can be used for serialization
		/// </summary>
		/// <returns></returns>
		private List<Type> GetKnownSerializationTypes()
		{
			List<Type> types = new List<Type>(internalRegisteredTypes);
			types.AddRange(registeredTypes);

			return types;
		}


		/// <summary>
		/// Serialize our selection to a string
		/// </summary>
		/// <returns></returns>
		private string SerializeSelectionToString()
		{
			StringWriter stringWriter = new StringWriter();
			XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter);

			// Our Layer elements
			DataContractSerializer serializer = new DataContractSerializer(
				layers.GetType(), // type
				"Layers", // root name
				"",
				GetKnownSerializationTypes(), // known types
				0x7FFF, // maxItemsInObjectGraph
				false, // ignoreExtensionDataObject
				true, // preserveObjectReferences
				null // dataContractSurrogate
				);

			serializer.WriteObject(xmlWriter, layers);

			return stringWriter.ToString();			
		}


		/// <summary>
		/// Deserialize the selection from a string
		/// </summary>
		/// <param name="line"></param>
		/// <returns></returns>
		private Selection DeserializeSelectionFromString(string line)
		{
			StringReader stringReader = new StringReader(line);
			XmlTextReader xmlReader = new XmlTextReader(stringReader);

			DataContractSerializer serializer = new DataContractSerializer(
				layers.GetType(), // type
				"Layers", // root name
				"",
				GetKnownSerializationTypes(), // known types
				0x7FFF, // maxItemsInObjectGraph
				false, // ignoreExtensionDataObject
				true, // preserveObjectReferences
				null // dataContractSurrogate
				);

			List<Layer> pastedLayers = (List<Layer>)serializer.ReadObject(xmlReader);
			List<Connector> pastedConnectors = new List<Connector>();

			selection.Clear();


			if (pastedLayers != null)
			{
				for (int i=0; i<layers.Count; ++i)
				{
					if (i < pastedLayers.Count)
					{
						Layer pastedLayer = pastedLayers[i];
						Layer originalLayer = layers[i];

						pastedConnectors.Clear();

						// Move all selected pasted nodes over to our own layer 
						// (no need to remove them from the pasted layer)
						foreach (Element e in pastedLayer)
						{
							Node node = e as Node;
							if (node != null && node.IsSelected)
							{
								selection.Add(node);
								originalLayer.Add(node);
								pastedConnectors.AddRange(node.Connectors);
							}							
						}

						// Move all cables that are attached to pasted nodes on both sides
						foreach (Element e in pastedLayer)
						{
							Cable cable = e as Cable;
							if (cable != null &&
								pastedConnectors.Contains(cable.From) &&
								pastedConnectors.Contains(cable.To))
							{
								originalLayer.Add(cable);
								selection.Add(cable);
							}
						}
					}
				}

				RecalculateElementAreas();
			}

			return selection;
		}
    }
}
